# -*- coding: utf-8 -*-

import os
import sys
import json
import base64
from time import time
from datetime import datetime, timedelta

from flask import Flask
from flask import redirect, url_for, render_template, flash, request, jsonify, session, current_app, make_response

from sqlalchemy.orm import relationship
from sqlalchemy.schema import Column, ForeignKey
from sqlalchemy.sql.sqltypes import Integer, String, Text, Time, Date, DateTime, Interval, PickleType

from flask_socketio import SocketIO, emit, join_room, leave_room

from flask_sqlalchemy import SQLAlchemy

from flask_migrate import Migrate

from werkzeug.datastructures import FileStorage

app = Flask(__name__)

app.config.update(dict(
    DEBUG=True,
    SECRET_KEY=os.getenv('secret_key', 'default'),
    JSON_AS_ASCII=False,
    MAX_CONTENT_LENGTH=100 * 1024 * 1024,
    SQLALCHEMY_DATABASE_URI='mysql://root:root@localhost/flask_production',  # uniform resource identifier
    SQLALCHEMY_TRACK_MODIFICATIONS=False,
    PERMANENT_SESSION_LIFETIME=timedelta(days=60),
))

socket = SocketIO(app)

db = SQLAlchemy(app)

migrate = Migrate(app, db)

online_device = dict()


class User(db.Model):
    username = Column(String(length=30), primary_key=True)
    password = Column(String(length=30), nullable=False)

    articles = relationship('Article', back_populates='author_info', cascade='save-update, merge, delete')

    def __repr__(self):
        return 'User: <username: {0}, password: {1}>'.format(self.username, self.password)


class Article(db.Model):
    id = Column(Integer, primary_key=True, autoincrement=True)
    title = Column(String(length=30), primary_key=True)
    author = Column(String(length=30), ForeignKey('user.username'), primary_key=True)
    content = Column(Text)
    create_time = Column(DateTime, nullable=False)
    edit_time = Column(DateTime, nullable=False)

    author_info = relationship('User', back_populates='articles')
    staticFiles = relationship('StaticFile', back_populates='article_belong', cascade='save-update, merge, delete')

    def __repr__(self):
        return 'Article: <title: {0}, author: {1}, id: {2}>'.format(self.title, self.author, self.id)


class StaticFile(db.Model):
    id = Column(Integer, primary_key=True, autoincrement=True)
    articleID = Column(Integer, ForeignKey('article.id'), primary_key=True)
    path = Column(String(length=300), nullable=False)

    article_belong = relationship('Article', back_populates='staticFiles')

    def __repr__(self):
        return 'StaticFile: <Article ID: {0}, path: {1}>'.format(self.articleID, self.path)


@app.before_request
def before_request():
    path = request.path  # type: str
    if path == '/login' or path == '/login/':
        return None
    else:
        username = session.get('username')
        login_time = session.get('login_time')

        if not username or login_time + 30 * 24 * 60 * 60 < time():
            return redirect(url_for('login'))
        elif User.query.get(username) is None:
            return redirect(url_for('login'))
        else:
            return None


@app.after_request
def after_request(resp):
    resp.headers['Access-Control-Allow-Origin'] = request.headers.get('Origin')
    resp.headers['Access-Control-Allow-Credentials'] = 'true'
    return resp


@app.errorhandler(404)
def error_handler_404(resp):
    return make_response(
        render_template('404.html', message=resp),
        404
    )


@app.errorhandler(500)
def error_handler_500(resp):
    return make_response(
        render_template('500.html', message=resp),
        500
    )


@app.route('/')
def index():
    return redirect(url_for('home'))


@app.route('/favicon.ico')
def favicon_ico():
    return current_app.send_static_file('favicon.ico')


@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'GET':
        return render_template('login.html')
    elif request.method == 'POST' and request.form.get('submit'):
        username = request.form.get('username')
        password = request.form.get('password')

        user = User.query.get(username)  # type: User

        if not user:
            flash('用户名或密码错误')
            return redirect(url_for('login'))
        elif user.password != password:
            flash('用户名或密码错误')
            return redirect(url_for('login'))
        else:
            session.permanent = True
            session['username'] = username
            session['login_time'] = int(time())
            return redirect(url_for('home'))
    else:
        return None


@app.route('/home', methods=['GET', 'POST'])
def home():
    username = session.get('username')
    user = User.query.get(username)  # type: User

    if request.method == 'GET':
        if request.args.get('debug'):
            return render_template('home.html', username=username, articles=user.articles)
        else:
            return render_template('主页测试.html')
    elif request.method == 'POST':
        if request.form.get('logout'):
            # session.pop('username')
            # session.pop('login_time')
            session.clear()
            return redirect(url_for('login'))


@app.route('/play_start')
def play_start():
    return render_template('放映.html')


@app.route('/edit', methods=['GET', 'POST'])
def edit():
    username = session.get('username')

    if request.method == 'GET':
        art_id = request.args.get('id')  # type: int
        art_title = request.args.get('title')  # type: str
        art = Article.query.get((art_id, art_title, username))  # type: Article

        session.permanent = True
        session['art_id'] = art_id
        session['art_title'] = art_title

        if request.args.get('debug'):
            return render_template('edit.html', art=art)
        else:
            return render_template('用户编辑.html')
    elif request.method == 'POST':
        art_id = request.form.get('id_debug')  # type: int
        art_title = request.form.get('title_debug')  # type: str
        if not art_id:
            art_id = session.get('art_id')
        if not art_title:
            art_title = session.get('art_title')
        art = Article.query.get((art_id, art_title, username))  # type: Article

        if request.form.get('edit'):
            if not art:
                return make_response(
                    render_template('404.html', message='article not found, please check title and id.'),
                    404
                )
            new_title = request.form.get('title')  # type: str
            new_title = new_title.strip()
            new_content = request.form.get('content')  # type: str
            new_content = new_content.strip()
            if len(new_title) > 30 or len(new_title) == 0:
                flash('标题在1-30以内')
                print('----- article edit not successful! ', art)
                return redirect(url_for('edit', title=art.title, id=art.id))
            if len(new_content) == 0:
                flash('内容不能为空！')
                print('----- article edit not successful! ', art)
                return redirect(url_for('edit', title=art.title, id=art.id))
            if new_title != art.title:
                art.title = new_title
            if new_content != art.content:
                art.content = new_content
            art.edit_time = datetime.now()
            db.session.commit()
            print('----- article edit successful: ', art)

        elif request.form.get('delete'):
            if not art:
                return make_response(
                    render_template('404.html', message='article not found, please check title and id.'),
                    404
                )
            print('----- article delete successful: ', art)
            db.session.delete(art)
            db.session.commit()

        elif request.form.get('create'):
            now = datetime.now()
            new_title = request.form.get('title')  # type: str
            new_title = new_title.strip()
            new_content = request.form.get('content')  # type: str
            new_content = new_content.strip()
            if len(new_title) > 30 or len(new_title) == 0:
                flash('标题在1-30字符以内')
                return redirect(url_for('edit'))
            if len(new_content) == 0:
                flash('内容不能为空！')
                return redirect(url_for('edit', title=art.title, id=art.id))

            new_art = Article(
                author=username,
                title=new_title,
                content=new_content,
                edit_time=now,
                create_time=now,
            )
            db.session.add(new_art)
            db.session.commit()
            print('----- article create successful: ', new_art)

        else:
            print('----- article modification not made!')
        return redirect(url_for('home'))


@app.route('/upload', methods=['GET', 'POST'])
def upload():
    if request.method == 'POST':
        device_name = request.form.get('device name')
        art_id = request.args.get('id')
        art_title = request.args.get('title')
        delay_time = request.args.get('time', 1000)
        username = session.get('username')
        art = Article.query.get((art_id, art_title, username))  # type: Article

        if art:
            data = dict(
                id=art_id,
                title=art_title,
                content=art.content,
                time=delay_time,
            )
            emit('content upload', json.dumps(data, ensure_ascii=False), namespace='/', room=device_name)
            return 'success'
        else:
            return 'error', 404
    if request.args.get('debug'):
        return render_template('upload.html', online_device=online_device)
    else:
        return render_template('上传页面.html')


@app.route('/doc')
def doc():
    return render_template('doc.html')  # 帮助文档


@app.route('/file_list', methods=['GET', 'POST'])
def file_list():
    if request.method == 'GET':
        return render_template('file_list.html')
    elif request.method == 'POST':
        __files = request.files.getlist('files')
        username = session.get('username')
        if __files[0].filename:
            for file in __files:  # type: FileStorage
                file.save('./static/users/' + username + '/' + file.filename)
                print('----- saving: ', file.filename, ' to', './static/users/' + username + '/')
        return redirect(url_for('home'))


@app.route("/view_file", methods=['GET', 'POST'])
def view_file():
    username = session.get('username')

    if request.method == 'POST' and request.form.get('delete'):
        delete_file_name = request.form.get('delete_file_name')

        try:
            os.remove('./static/users/' + username + '/' + delete_file_name)
            print('----- File: \"' + delete_file_name + '\" has been deleted from /static/users/' + username + '/')
        except FileNotFoundError:
            sys.stderr.write("!!! - file not found: " + delete_file_name)
            flash('删除文件 \"' + delete_file_name + "\"发生错误")

        return 'success'

    file_name = os.listdir('./static/users/' + username + '/')
    file_type = []
    file_size = []
    for f in file_name:
        file_type.append(f.split('.')[-1])
        file_size.append(os.path.getsize('./static/users/' + username + '/' + f) / 1024)
    return render_template(
        'view_file.html',
        username=username,
        num=len(file_name),
        file_name=file_name,
        file_type=file_type,
        file_size=file_size,
    )


@app.route('/terminal_debug')
def terminal_debug():
    username = session.get('username')
    return render_template('terminal.html', username=username)


@app.route('/terminal_actual')
def terminal_actual():
    username = session.get('username')
    return render_template('真实的终端.html', username=username)


@app.route('/json/view_file', methods=['GET', 'POST'])
def json_view_file():
    username = session.get('username')
    file_name = os.listdir('./static/users/' + username + '/')
    json_data = {
        'server_path': '/static/users/' + username + '/',
        'file_names': file_name,
    }
    return jsonify(json_data)


@app.route('/json/username', methods=['GET', 'POST'])
def json_username():
    username = session.get('username')
    return jsonify(dict(username=username))


@app.route('/json/article_list', methods=['GET', 'POST'])
def json_article_list():
    username = session.get('username')
    user = User.query.get(username)  # type: User
    arts = {}
    for art in user.articles:
        tmp = dict(
            id=art.id,
            title=art.title,
            author=art.author,
            content=art.content,
            create_time=art.create_time,
            edit_time=art.edit_time,
        )
        arts[art.id] = tmp
    return jsonify(arts)


@app.route('/json/article_query', methods=['GET', 'POST'])
def json_article_query():
    username = session.get('username')
    art_id = request.args.get('id')
    art_title = request.args.get('title')

    art = Article.query.get((art_id, art_title, username))  # type: Article
    if art:
        return jsonify(dict(
            id=art.id,
            title=art.title,
            author=art.author,
            content=art.content,
            create_time=art.create_time,
            edit_time=art.edit_time,
        ))
    else:
        return jsonify()


@app.route('/json/online_device_list', methods=['GET', 'POST'])
def json_online_device_list():
    res = list()
    for key in online_device:
        if online_device[key] != 'upload page' and online_device[key]:
            res.append(dict(
                id=key,
                name=online_device[key]
            ))
    return jsonify(res)


@app.route('/json/content_upload', methods=['GET', 'POST'])
def json_content_upload():
    device_id = request.args.get('device_id')
    art_id = request.args.get('art_id')
    art_title = request.args.get('art_title')
    delay_time = request.args.get('time', 1000)

    if not device_id or not art_title or not art_title:
        return '参数错误', 404

    device_name = online_device.get(device_id)
    art = Article.query.get((art_id, art_title, session.get('username')))  # type: Article

    if not device_name or not art:
        return '参数错误', 404

    data = dict(
        id=art_id,
        title=art_title,
        time=delay_time,
        cotent=art.content,
    )

    emit('content upload', json.dumps(data, ensure_ascii=False), namespace='/', room=device_name)
    return '成功投递'


@app.route('/json/upload_base64', methods=['GET', 'POST'])
def json_upload_base64():
    filename = request.form.get('filename')  # 从表单获取文件名
    content = request.form.get('content')  # 从表单获取bse64码
    articleID = request.form.get('id_debug') # 从请求数据中获取文件所属的文章ID
    username = session.get('username')  # 从cookie获取用户名
    image_data = base64.b64decode(content)
    file_path = './static/users/' + username + '/' + filename

    static_file = StaticFile(articleID=articleID, path=file_path)

    try:
        db.session.add(static_file)
        db.session.commit()
    except Exception as e:
        print(e)
        return 'save failed'

    f = open('./static/users/' + username + '/' + filename, 'wb')  # 储存文件
    f.write(image_data)
    f.close()
    print('----- saving: ', filename, ' to', './static/users/' + username + '/')
    return 'success'


@app.route('/json/upload_base64_multiple', methods=['GET', 'POST'])
def json_upload_base64_multiple():
    filenames = request.form.get('filenames')
    filenames = json.loads(filenames)
    contents = request.form.get('contents')
    contents = json.loads(contents)
    username = session.get('username')

    for i in range(len(filenames)):
        filename = filenames[i]
        content = contents[i]
        image_data = base64.b64decode(content)
        f = open('./static/users/' + username + '/' + filename, 'wb')
        f.write(image_data)
        f.close()
        print('----- saving: ', filename, ' to', './static/users/' + username + '/')
    return 'success'


@app.route('/json/get_base64', methods=['GET', 'POST'])
def json_get_base64():
    filename = request.form.get('filename')  # type: str
    username = session.get('username')  # type: str
    try:
        f = open('./static/users/' + username + '/' + filename, 'rb')
    except FileNotFoundError:
        return make_response(
            render_template('404.html', message='file not found, please check filename.'),
            404
        )
    else:
        image_data = base64.b64encode(f.read())
        print('----- loading: ', filename, ' from ./static/users/' + username + '/')
        return image_data  # 返回base64码


@app.route('/json/get_base64_multiple', methods=['GET', 'POST'])
def json_get_base64_multiple():
    filenames = request.form.get('filenames')
    username = session.get('username')
    filenames = json.loads(filenames)
    res = dict()
    for i in filenames:
        try:
            f = open('./static/users/' + username + '/' + i, 'rb')
            image_data = base64.b64encode(f.read())  # type: bytes
            res[i] = image_data.decode('utf-8')
            f.close()
            print('----- loading: ', i, ' from ./static/users/' + username + '/')
        except FileNotFoundError:
            return make_response(
                render_template('404.html', message='file not found, please check filename.'),
                404
            )
    return jsonify(res)


@app.route('/json/create_article', methods=['GET', 'POST'])
def json_create_article():
    username = session.get('username')
    title = request.form.get('title')
    if not title:
        title = 'new title'
    title = title.strip()

    new_article = Article(
        title=title,
        author=username,
        content='',
        edit_time=datetime.now(),
        create_time=datetime.now()
    )

    db.session.add(new_article)
    db.session.commit()

    return jsonify(dict(
        id=new_article.id,
        title=new_article.title,
        author=new_article.author,
        content=new_article.content,
        create_time=new_article.create_time,
        edit_time=new_article.edit_time,
    ))


@app.template_global()  # 模板全局函数
def length(arg):
    return len(arg)


@socket.on('connect')
def socket_on_connect():
    print('----- socket: ' + request.sid + ' is connected.')
    online_device[request.sid] = ''


@socket.on('first_connection')
def socket_on_first_connection(message):
    device_name = message.get('device name')
    if device_name:
        print('----- socket: ' + device_name + ' is online')
        online_device[request.sid] = device_name
        join_room(device_name)
        if device_name != 'upload page':
            emit('device change', 'online device has changed.', namespace='/', room='upload page')


@socket.on('disconnect')
def socket_on_disconnect():
    sid = request.sid
    device_name = online_device.get(sid)
    online_device.pop(sid)
    print('----- socket: ' + device_name + ' is now offline.')
    if device_name != 'upload page':
        emit('device change', 'online device has changed.', namespace='/', room='upload page')


@db.event.listens_for(StaticFile, 'after_delete')
def static_file_deletion(*args):
    static_file = args[2]  # type: StaticFile
    try:
        os.remove(static_file.path)
    except FileNotFoundError:
        print('----- static file deletion failed: ' + str(static_file) + ' is not found!')
    else:
        print('----- static file drop successful: ' + str(static_file))


if __name__ == '__main__':
    socket.run(app)
    # flask run --host=0.0.0.0 --port=5000 --with-threads
